/*
  S.M.A.C.K - An operating system kernel
  Copyright (C) 2010,2011 Mattias Holm and Kristian Rietveld
  For licensing and a full list of authors of the kernel, see the files
  COPYING and AUTHORS.
*/

#include <stdint.h>
#include <stddef.h>
#include <interrupt.h>
#include <timer.h>
#include <vm.h>
#include <omap-clock.h>

struct gpt
{
  uint32_t tidr;
  uint32_t padding0[3];
  uint32_t tiocp_cfg;
  uint32_t tistat;
  uint32_t tisr;
  uint32_t tier;
  uint32_t twer;
  uint32_t tclr;
  uint32_t tcrr;
  uint32_t tldr;
  uint32_t ttgr;
  uint32_t twps;
  uint32_t tmar;
  uint32_t tcar1;
  uint32_t tsicr;
  uint32_t tcar2;
  uint32_t tpir;
  uint32_t tnir;
  uint32_t tcvr;
  uint32_t tocr;
  uint32_t towr;
};

static volatile struct gpt *gpt1;

struct sync_timer
{
  uint32_t reg_32ksyncnt_rev;
  uint32_t reg_32ksyncnt_sysconfig;
  uint32_t padding0[2];
  uint32_t reg_32ksyncnt_cr;
};

static volatile struct sync_timer *sync_timer = NULL;

#define GPT1_TISR_MAT_IT_FLAG 0  /* Match interrupt flag */
#define GPT1_TIER_MAT_IT_ENA 0  /* Match interrupt enable */
#define GPT1_TCLR_CE 6 /* Capture events */
#define GPT1_TCLR_ST 0 /* Start */

/* Usually, GPT1 ticks at a frequency of 32768 KHz.  To get interrupts
 * at a rate of roughly 100 Hz, we need 328 ticks per interrupt.
 */
#define TICKS_PER_INTERRUPT 328
#define TICKS_PER_MSEC 32

void timer_irq_handler(void *data);

void
hw_timer_init(void)
{
  /* FIXME: Ideally, this is done somewhere else. (bsp_init?) */
  omap_clock_init ();

  gpt1 = vm_map_physical(vm_get_kernel_map(VM_REG_DEVICE), VM_DEVICE | VM_SUPER_RW,
                         (uintptr_t)0x48318000, 4096);

  sync_timer = vm_map_physical(vm_get_kernel_map(VM_REG_DEVICE), VM_DEVICE | VM_SUPER_RW,
                               (uintptr_t)0x48320000, 4096);

  /* Set out GPT1 as 32KHz timer */
  gpt1->tldr = 0x0;
  gpt1->tmar = TICKS_PER_INTERRUPT;
  gpt1->tclr = 1 << GPT1_TCLR_CE | 1 << GPT1_TCLR_ST;
  gpt1->ttgr = 0x0;

  /* Make sure the GPT1 clock is active */
  omap_clock_enable_gpt1 ();

  /* Enable match interrupt */
  gpt1->tier = 1 << GPT1_TIER_MAT_IT_ENA;

  interrupt_register_irq_handler(GPT1_IRQ, timer_irq_handler, NULL);
  unmask_interrupt(GPT1_IRQ);
}

void
timer_irq_handler(void *data)
{
  uint32_t tisr = gpt1->tisr;

  if ((tisr & (1 << GPT1_TISR_MAT_IT_FLAG)) != (1 << GPT1_TISR_MAT_IT_FLAG))
    return;

  timer_call_handler();

  /* Reset counter */
  gpt1->ttgr = 0x0;

  /* Reset interrupt */
  gpt1->tisr = tisr;
}

/* For usage in the kernel only.  For user-space there has to be
 * a proper system call, that will put the process to sleep and
 * gives other processes a chance to run.
 */
void
msleep(int msec)
{
  int ticks = msec * TICKS_PER_MSEC;
  unsigned int start_tick;

  /* FIXME: Handle overflows of the register */
  start_tick = sync_timer->reg_32ksyncnt_cr;
  while (start_tick + ticks > sync_timer->reg_32ksyncnt_cr);
}
